# 3.1 导入蓝图对象
import random
import re
from datetime import datetime

from flask import request, make_response, current_app, jsonify, session

from info import redis_store, constants, db
from info.libs.yuntongxun.sms import CCP
from info.models import User
from info.utils.response_code import RET
from . import passport_blue
from info.utils.captcha.captcha import captcha


# 3.2 使用蓝图实现路由

# 开发中, 接口的地址和参数是后端人员确定的(也可以和前端商量)


@passport_blue.route("/logout", methods=['POST'])
def logout():
    """
    清除session中的对应登录之后保存的信息
    """
    session.pop('user_id', None)
    session.pop('nick_name', None)
    session.pop('mobile', None)

    # 返回结果
    return jsonify(errno=RET.OK, errmsg="OK")


# 参数: 手机号\密码
@passport_blue.route('/login', methods=["POST"])
def login():
    """
    1. 获取参数和判断是否有值
    2. 从数据库查询出指定的用户
    3. 校验密码
    4. 保存用户登录状态
    5. 返回结果
    """
    # 1. 获取参数和判断是否有值
    json_dict = request.json
    mobile = json_dict.get('mobile')
    password = json_dict.get('password')

    if not all([mobile, password]):
        return jsonify(errno=RET.PARAMERR, errmsg='参数不全')

    if not re.match(r'1[3456789]\d{9}', mobile):
        return jsonify(errno=RET.DATAERR, errmsg='手机号错误')

    # 2. 从数据库查询出指定的用户
    try:
        user = User.query.filter_by(mobile=mobile).first()
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='查询数据库错误')

    if not user:
        # 用户不存在
        return jsonify(errno=RET.NODATA, errmsg='用户名不存在')

    # 3. 校验密码
    if not user.check_passowrd(password):
        return jsonify(errno=RET.PWDERR, errmsg='用户名或密码错误')

    # 为了防止暴力破解, 建议放到一起写
    # if not user or user.password_hash != password:

    # 4. 保存用户登录状态
    session['user_id'] = user.id
    session['nick_name'] = user.nick_name
    session['mobile'] = user.mobile

    # 设置最后登录的时间
    # 修改了模型属性之后, 需要提交 --> 自动提交-->需要配置
    user.last_login = datetime.now()

    # 5. 返回成功信息
    return jsonify(errno=RET.OK, errmsg='登录成功')


"""
注册用户
请求方式: POST
URL: /passport/register
参数: mobile, sms_code, password
"""


@passport_blue.route('/register', methods=['POST'])
def register():
    '''
    1. 获取参数
    2. 校验参数（完整性，手机号）
    3. 从redis获取短信验证码（判断过期）
    4. 对比验证码，对比失败返回错误
    5. 对比成功，注册用户（1. 判断手机号是否注册过 2. 创建User模型 3. 添加到数据库）
    6. 实现登录 —> 设置session
    7. 返回成功信息—> 前端接受到了成功信息， 刷新界面即可
    '''
    # 一. 获取参数
    # 1. 获取参数
    json_dict = request.json
    mobile = json_dict.get('mobile')
    sms_code = json_dict.get('sms_code')
    password = json_dict.get('password')

    # 二. 校验参数
    # 2. 校验参数（完整性，手机号）
    if not all([mobile, sms_code, password]):
        return jsonify(errno=RET.PARAMERR, errmsg='参数不全')

    if not re.match(r'1[3456789]\d{9}', mobile):
        return jsonify(errno=RET.DATAERR, errmsg='手机号错误')

    # 三. 逻辑处理
    # 3. 从redis获取短信验证码（判断过期）
    try:
        real_sms_code = redis_store.get('SMS_' + mobile)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='数据库查询失败')

    if not real_sms_code:
        return jsonify(errno=RET.NODATA, errmsg='验证码已过期或手机号错误')

    # 4. 对比验证码，对比失败返回错误
    if real_sms_code != sms_code:
        return jsonify(errno=RET.DATAERR, errmsg='验证码输入错误')

    # 删除验证码(对比完成,就没用了,虽然过期就删自动删除,但是为了保险(安全/逻辑完善))-->及时删除
    try:
        redis_store.delete('SMS_' + mobile)
    except Exception as e:
        current_app.logger.error(e)
        # 从逻辑严谨性上来说, 有错误就返回错误信息, 让前端处理.
        # 从产品经理的角度(用户体验角度), 不要返回错误信息
        # return jsonify(errno=RET.DBERR, errmsg='数据库查询失败')

    # 5. 对比成功，注册用户（1. 判断手机号是否注册过 2. 创建User模型 3. 添加到数据库）
    # 5.1 判断手机号是否注册过
    try:
        user = User.query.filter_by(mobile=mobile).first()
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='数据库查询失败')

    if user:
        # 用户已经存在
        return jsonify(errno=RET.DATAEXIST, errmsg='该用户已注册')

    # 5.2 创建User模型
    user = User()
    user.nick_name = mobile
    user.mobile = mobile
    # TODO (huizhubo) 密码加密未处理
    # user.password_hash = password
    # pbkdf2:sha256:50000$anyUJR6D$a62064a7048598cabe7624d34688b2601ab6350567fd2cab48fd17ea3799b4f2
    user.password = password

    # 5.3 添加到数据库
    try:
        db.session.add(user)
        db.session.commit()
    except Exception as e:
        # 数据库修改操作,失败要回滚
        db.session.rollback()
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='数据库添加失败')

    # 6. 实现登录 —> 设置session
    session['user_id'] = user.id
    session['nick_name'] = user.nick_name
    session['mobile'] = user.mobile

    # 四. 返回数据
    # 7. 返回成功信息—> 前端接受到了成功信息， 刷新界面即可
    return jsonify(errno=RET.OK, errmsg='注册成功')


"""
获取短信验证码
URL: 127.0.0.1:5000/passport/sms_code?
参数: mobile, image_code, image_code_id
"""


@passport_blue.route('/sms_code', methods=['POST'])
def get_sms_code():
    # 代码步骤分析

    # 一. 获取参数
    # 1. 获取参数(手机号\图像验证码内容, 图像验证码ID)
    # request.data接收的是json格式的字符串
    # json.loads(request.data)
    params_dict = request.json
    mobile = params_dict.get('mobile')
    image_code = params_dict.get('image_code')
    image_code_id = params_dict.get('image_code_id')

    # 二. 校验数据
    # 2. 校验数据(1. 数据完全性, 2. 正则匹配手机号)
    if not all([mobile, image_code, image_code_id]):
        # 如果有参数不全, 则会进入分支中
        return jsonify(errno=RET.PARAMERR, errmsg='参数不全')

    if not re.match('1[3456789][0-9]{9}', mobile):
        return jsonify(errno=RET.PARAMERR, errmsg='手机号格式有误')

    # 三. 逻辑处理
    # 3. 先从redis中获取数据(需要判断是否有数据)try
    try:
        real_image_code = redis_store.get('Image_code_id_' + image_code_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='查询数据失败')

    # 要判断redis过期的情况
    if not real_image_code:
        return jsonify(errno=RET.NODATA, errmsg='验证码已过期')

    # 4. 用户传入的于服务器的验证码做对比(如果失败, 返回错误信息JSON)

    # 4.1 无论对比成功与否, 只要获取到了服务器的验证码,就可以删除
    try:
        redis_store.delete('Image_code_id_' + image_code_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='删除数据失败')

    # 4.2 对比
    if real_image_code.upper() != image_code.upper():
        return jsonify(errno=RET.DATAERR, errmsg='验证码输入错误')

    # 4.3 应该在对比完图像验证码后, 再去查询用户是否存在
    try:
        user = User.query.filter_by(mobile=mobile).first()
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='数据库查询失败')

    if user:
        # 用户已经存在
        return jsonify(errno=RET.DATAEXIST, errmsg='该用户已注册')

    # 5. 如果对比一致, 生成验证码的值(第三方只负责发短信 '0000'), 保存到redis中
    # 06d: 以6位返回, 如果不足6位, 以0补齐
    sms_code_str = '%06d' % random.randint(0, 999999)
    current_app.logger.debug('sms_code: %s' % sms_code_str)

    # 6. 调用第三方发送短信(第三方框架有问题)
    # result = CCP().send_template_sms(mobile, [sms_code_str, 5], 1)
    # if result != 0:
    #     # 表示发送失败
    #     return jsonify(errno=RET.THIRDERR, errmsg='短信发送失败')

    # 保存到redis中
    try:
        redis_store.set('SMS_' + mobile, sms_code_str, constants.SMS_CODE_REDIS_EXPIRES)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='短信验证码保存redis失败')

    # 四. 返回数据
    # 7. 返回成功的数据
    return jsonify(errno=RET.OK, errmsg='短信验证码发送成功')


# 获取图像验证码的路由/接口
# URL: /passport/image_code?image_code_id=XXXXXXXXXX
# 参数: image_code_id
@passport_blue.route('/image_code')
def get_image_code():

    # 1. 获取参数ID
    image_code_id = request.args.get('image_code_id')

    # 2. 生成验证码内容
    name, text, image_data = captcha.generate_captcha()

    # 3. 保存到redis中 try:
    try:
        # 1. redis_store为了有智能提示, 可以增加类型注释
        # 2. set本身也是可以设置过期时间的, 在参数的第三个位置
        redis_store.set('Image_code_id_' + image_code_id, text, constants.IMAGE_CODE_REDIS_EXPIRES)
    except Exception as e:
        # 记录到日志中
        current_app.logger.error(e)
        # 返回一个错误信息 前后端的数据, 采用JSON格式来返回. 可以通知前端局部数据的处理
        # {'errno': '4001', errmsg:'保存图片验证码失败'}
        return jsonify(errno=RET.DBERR, errmsg='保存图片验证码失败')

    # 4. 返回图像
    response = make_response(image_data)
    response.headers['Content-Type'] = 'image/jpg'
    return response
